﻿using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace SuperKit
{
    public class DivideCatalog : CommandExecutor {
        public bool IsNonParent { get; }
        public bool IsNonFile { get; }
        public DirectoryInfo DirInfo { get; private set; }
        public List<FileGroup> Groups { get; private set; }
        public string Spliter { get; }
        /// <summary>最多将一个文件分成几级的目录</summary>
        public int MaxSplit { get; }
        /// <summary>是否是根据名称来决定相似性</summary>
        public bool IsNameCatalog { get; }
        /// <summary>是否是根据分辨率来决定相似性</summary>
        public bool IsSizeCatalog { get; }
        /// <summary>是否是相似性来决定决定相似性</summary>
        public bool IsSameCatalog { get; }
        /// <summary>是否保留分类目录名称，如果为否，则在分类目录之后，将原来的名称替换掉。</summary>
        public bool IsCutName { get; }
        /// <summary>是否在名称面前追加图片数量</summary>
        public bool IsAddNumber { get; }
        /// <summary>是否在名称面前追加分辨率</summary>
        public bool IsAddSize { get; }
        /// <summary>是否在名称面前追加分辨率（按平均算）</summary>
        public bool IsAddSizeAvg { get; }
        /// <summary>根据相似性进行分类的时候，决定相似度</summary>
        public float SameRate { get; }
        /// <summary>在比较相似性的时候，是否需要忽略大小，不配置时，默认先比较分辨率大小，大小不相同则为不同类</summary>
        public bool IgnoreSize { get; }

        public DivideCatalog(ArgReader arg, IExecutorOwner form, string fpath) : base(arg, form, fpath) {
            IsNonParent = arg.IsContain("-nonParent");
            IsNonFile = arg.IsContain("-nonFile");
            Spliter = arg.GetParameter("-spliter", "_");
            MaxSplit = arg.GetInteger("-maxSplit", 2);
            IsNameCatalog = arg.IsContain("-nameCatalog");
            IsCutName = arg.IsContain("-cutName");
            IsSizeCatalog = arg.IsContain("-sizeCatalog");
            IsSameCatalog = arg.IsContain("-sameCatalog");
            IsAddNumber = arg.IsContain("-addNumber");
            IsAddSize = arg.IsContain("-addSize");
            IsAddSizeAvg = arg.IsContain("-addSizeAvg");
            SameRate = arg.GetFloat("-sameRate", 0.618f);
            IgnoreSize = arg.IsContain("-IgnoreSize");
        }

        public override void Execute() {
            base.Execute();
            Thread t = new Thread(ExecuteThread);
            t.Start();
        }

        private void ExecuteThread() {
            DirInfo = new DirectoryInfo(FilePath);
            Groups = FileGroup.GetGroups(DirInfo);

            foreach (var i in Groups) {
                if (LayerEnable(i.Layer)) {
                    if (IsNameCatalog) {
                        ProcessName(i);
                    } else if (IsSizeCatalog) {
                        ProcessSize(i);
                    } else if (IsSameCatalog) {
                        ProcessSame(i);
                    } else {
                        ProcessSplit(i);
                    }
                }
            }

            End();
        }

        private void ProcessSame(FileGroup group) {
            ImageSameGroup g = new ImageSameGroup(group, SameRate, IgnoreSize, MaxThread, SetStatus);
            g.Analysis();

            foreach (var i in g.Items) {
                Flush("{0}, {1}个。", i.Name, i.Count);
            }

            foreach (var i in g.Items) {
                foreach (var f in i.Items) {
                    Move(i, f.item.file);
                }
            }
        }

        private void Move(ImageSameGroupItem item, FileInfo file) {
            string newDir = GetCreateDirectory(item.Name, item.Size, item.Count, file);
            MoveFile(file.FullName, Path.Combine(newDir, file.Name));
        }

        private void ProcessSize(FileGroup group) {
            ImageSizeGroup g = new ImageSizeGroup(group, MaxThread, SetStatus);
            g.Analysis();

            foreach (var i in g.Items) {
                foreach (var f in i.Files) {
                    Move(i, f);
                }
            }
        }

        private void Move(ImageSizeGroupItem item, FileInfo file) {
            string newDir = GetCreateDirectory(item.Name, item.Size, item.Count, file);
            MoveFile(file.FullName, Path.Combine(newDir, file.Name));
        }

        private void ProcessName(FileGroup group) {
            FileNameGroup g = new FileNameGroup(this, group);
            g.Analysis();
            
            foreach (var i in g.Items) {
                foreach (var f in i.Files) {
                    Move(i, f);
                }
            }

            Print(g);
        }

        private void Move(FileNameGroupItem item, FileInfo file) {
            string newDir = GetCreateDirectory(item.TrimName, Size.Empty, item.Count, file);
            var newName = IsCutName ? item.CutHeander(file) : file.Name;
            MoveFile(file.FullName, Path.Combine(newDir, newName));
        }

        private string GetCreateDirectory(string trimName, Size size, int count, FileInfo file) {
            var name = trimName;
            if (IsAddSize) {
                name = string.Format("{0}x{1}_{1}", size.Width, size.Height, name);
            }
            if (IsAddSizeAvg) {
                var avg = (int)Math.Floor(Math.Sqrt(size.Width * size.Height) * 0.01f) * 100;
                name = string.Format("{0}_{1}", avg, name);
            }
            if (IsAddNumber) {
                name = string.Format("{0}_{1}", count, name);
            }
            var newDir = Path.Combine(file.DirectoryName, name);
            if (Directory.Exists(newDir) == false) {
                Directory.CreateDirectory(newDir);
            }

            return newDir;
        }

        private void Print(FileNameGroup g) {
            foreach (var i in g.Items) {
                Flush("Catalog:{0}, Count:{1}", i.Name, i.Files.Count);
            }
        }

        private void ProcessSplit(FileGroup group) {
            foreach (var i in group.Files) {
                ProcessSplit(i);
            }
        }


        private List<string> divide = new List<string>();

        private void ProcessSplit(FileInfo file) {
            divide.Clear();
            string nameLeft = Path.GetFileNameWithoutExtension(file.Name);
            for (int i = 0; i < MaxSplit; i++) {
                int idx = nameLeft.IndexOf(Spliter);
                if (idx == -1) {
                    break;
                }
                var catalog = nameLeft.Substring(0, idx);
                divide.Add(catalog);

                nameLeft = nameLeft.Substring(idx + 1);
            }        

            // 如果Divide是空的，则表示没有任何分隔，不做处理
            if (divide.Count == 0) {
                return;
            }

            // 拼合最终的目录
            var newDir = file.Directory.FullName;
            foreach (var i in divide) {
                newDir = Path.Combine(newDir, i);
            }

            // 建立新的目录
            if (Directory.Exists(newDir) == false) {
                Directory.CreateDirectory(newDir);
            }

            // 剩下的文件名就是当前目录下的文件名
            var newFile = Path.Combine(newDir, nameLeft + Path.GetExtension(file.Name));
            // 判断文件是否存在
            if (File.Exists(newFile)) {
                Flush("{0} 已经存在！", newFile);
                return;
            }

            FlushMove(file.FullName, newFile);

            // 进行移动
            if (IsTest) { return; }

            MoveFile(file.FullName, newFile);
        }

        private void ClearDirs(DirectoryInfo[] dirs) {
            foreach (var i in dirs) {
                var files = i.GetFiles();
                var subs = i.GetDirectories();
                var count = (files == null ? 0 : files.Length) + (subs == null ? 0 : subs.Length);
                if (count == 0) {
                    i.Delete();
                }
            }
        }

        private void MoveDirs(DirectoryInfo[] dirs) {
            foreach (var i in dirs) {
                ProcessDir(i);
            }
        }

        private void ProcessFile(FileInfo f) {
            var newName = GetNewName(f);
            if (File.Exists(newName)) {
                Flush("{0} is exist!", newName);
                return;
            }
            Flush("Move From:{0}", f.FullName);
            Flush("Move To  :{0}", newName);

            try {
                File.Move(f.FullName, newName);
            } catch (Exception e) {
                Flush(e.Message);
            }

        }

        private string GetNewName(FileInfo f) {
            if (IsNonParent) {
                return Path.Combine(FilePath, String.Format("{0}", f.Name));
            } else {
                return Path.Combine(FilePath, String.Format("{0}_{1}", f.Directory.Name, f.Name));
            }
        }

        private void ProcessDir(DirectoryInfo dir) {
            var dirs = dir.GetDirectories();
            if (!Kit.IsEmpty(dirs)) {
                foreach (var i in dirs) {
                    MoveDir(dir, i);
                }
            }

            if (IsNonFile == false) {
                var files = dir.GetFiles();
                if (!Kit.IsEmpty(files)) {
                    MoveFiles(files);
                }
            }
        }

        private void MoveFiles(FileInfo[] files) {
            foreach (var i in files) {
                ProcessFile(i);
            }
        }

        private void MoveDir(DirectoryInfo parent, DirectoryInfo dir) {
            var newName = GetNewDir(parent, dir); //Path.Combine(parent.Parent.FullName, String.Format("{0}_{1}", parent.Name, dir.Name));
            if (Directory.Exists(newName)) {
                Flush("{0} is exist!", newName);
                return;
            }
            Flush("Move From:{0}", dir.FullName);
            Flush("Move To  :{0}", newName);

            try {
                Directory.Move(dir.FullName, newName);
            } catch (Exception e) {
                Flush(e.Message);
            }

        }

        private string GetNewDir(DirectoryInfo parent, DirectoryInfo dir) {
            if (IsNonParent) {
                return Path.Combine(parent.Parent.FullName, String.Format("{0}", dir.Name));
            } else {
                return Path.Combine(parent.Parent.FullName, String.Format("{0}_{1}", parent.Name, dir.Name));
            }
        }
    }
}
